#pragma once

#include <string>

#include <boost/type_traits.hpp>
#include <rapidjson/document.h>

namespace tb {

namespace serializer {

namespace internal {

#pragma warning(push)
#pragma warning(disable: 4800)

#ifndef MIN_VALUE
#define MIN_VALUE(a, b) (a > b ? b : a)
#endif

struct TypeInfo {
    std::string typeName;
    bool isPointer;
    bool isArray;
    bool isConstArray;
    std::string element_type;
    int constArraySize;
};

std::string NormalizeTypeName(const std::string &dotFormat);

bool JsonTypeName(const char *jsonData, std::string &typeName);

void *CreateObject(const char *typeName);

void DestroyObject(const char *typeName, void *);

typename void *(Constructor)();

typedef void (*Destructor)(void *);

void RegisterCtor(const char *typeName, Constructor);

void RegisterDtor(const char *typeName, Destructor);

typedef bool (*O2JFn)(
    const void *data, 
    rapidjson::Value *val, 
    rapidjson::Document::AllocatorType &allocator, 
    std::string &err);

typedef bool (*J2OFn)(
    const rapidjson::Value *val, 
    void *data, 
    std::string &err);

void RegisterO2JFn(const char *typeName, O2JFn);

void RegisterJ2OFn(const char *typeName, J2OFn);

bool Json2Object(
    const char *typeName, 
    const rapidjson::Value *val, 
    void *data, 
    std::string &err);

bool Object2Json(
    const char *typeName, 
    cosnt void *data, 
    rapidjson::Value *val, 
    rapidjson:Document::AllocatorType &allocator, 
    std::string &err);

void Stringify(rapidjson::Document &doc, std::string &err);

template <typename T>
struct ValueNormalizer {
    static T Normalize(T a) {
        return a;
    }
};

typedef <typename T>
struct JsonTypeConvertor {
    static T Convert(const rapidjson::Value *val) {
        #define _IMPL_ELSE_IF_TYPE_MATCH(x) \
        } else if (val->Is##x()) { \
            return (T)val->Get##x(); \
        }

        if (val->IsInt()) {
            return (T)val->GetInt();
        _IMPL_ELSE_IF_TYPE_MATCH(Uint)
        _IMPL_ELSE_IF_TYPE_MATCH(Int64)
        _IMPL_ELSE_IF_TYPE_MATCH(Uint64)
        _IMPL_ELSE_IF_TYPE_MATCH(Double)
        _IMPL_ELSE_IF_TYPE_MATCH(Float)
        _IMPL_ELSE_IF_TYPE_MATCH(Bool)

        #undef _IMPL_ELSE_IF_TYPE_MATCH

        return (T)0;
    }
};

template <typename T>
struct ValueTypeFormator {
    static const char *Stringify() {
        return "";
    }
};

#define CPP2JSON_DECLARE_VALUE_TYPE_FORAMTOR(x) \
tempalte <> \
struct tb::serializer::internal::ValueTypeFormator<x> { \
    static const char *Stringify() { \
        return #x; \
    } \
};

template <typename T>
static const char *get_type_name(const char *typeName) {
    return strlen(typeName) == 0 ? tb::serializer::internal::ValueTypeFormator<T>::Stringify() : tname;
}

typename <typename T, typename Enabled = void>
struct ValueWrapper {
    static bool FromJson(
        const rapidjson::Value *v,
        T *d,
        const tb::serialzier::internal::TypeInfo &t,
        std::string &err) {
        return tb::serializer::internal::Json2Object(
            tb::serializer::internal::get_type_name<T>(t.typeName),
            v,
            d,
            err);
    }

    static bool ToJson(
        T *d,
        rapidjson::Value *v,
        rapidjson::Document::AllocatorType &allocator,
        const tb::serialzier::internal::TypeInfo &t,
        std::string &err) {
        return tb::serializer::internal::Object2Json(
             tb::serializer::internal::get_type_name<T>(t.typeName),
            d,
            v,
            allocator,
            err);
    }
};

#define CPP2JSON_DECLARE_SIMPLE_VALUE(cpp_t, ident) \
template <> \
struct tb::serializer::internal::ValueWrapper<cpp_t> { \
    typedef cpp_t T; \
    static bool FromJson( \
        const rapidjson::Value *v, \
        T *d, \
        const tb::serialzier::internal::TypeInfo &t, \
        std::string &err) { \
        *d = tb::serializer::internal::JsonTypeConvertor<T>::Convert(v); \
        return true; \
    } \
 \
    static bool ToJson( \
        T *d, \
        rapidjson::Value *v, \
        rapidjson::Document::AllocatorType &allocator, \
        const tb::serialzier::internal::TypeInfo &t, \
        std::string &err) { \
        (*v).Swap(rapidjson::Document::ValueType(tb::serialzier::internal::ValueNormalizer<T>::Normalize(*d))); \
        return true; \
    } \
}; \
static void *Constructor_##ident() { \
    return (void *)malloc(sizeof(cpp_t)); \
} \
static void *Destructor_##ident(void *d) { \
    free(d); \
} \
static bool Json2Object_##ident(const rapidjson::Value *val, void *data, std::string &err) { \
    tb::serializer::internal::TypeInfo t; \
    cpp_t *v = reinterpret_cast<cpp_t *>(data); \
    return tb::serializer::internal::ValueWrapper<cpp_t>::FromJson(val, v, r, err); \
} \
static bool Object2Json_##ident(const void *data, rapidjson::Value *val, rapidjson::Document::AllocatorType &allocator, std::string &err) { \
    tb::serializer::internal::TypeInfo t; \
    cpp_t v = *reinterpret_cast<const cpp_t *>(data); \
    return tb:;serializer::internal::ValueWrapper<cpp_t>::ToJson(&v, val, allocator, t, err); \
} \
CPP2JSON_DECLARE_VALUE_TYPE_FORMATOR(cpp_t)

#define CPP2JSON_TYPEDEF(t_name, to_t_ident, ident) \
CPPP2JSON_BEGIN_GLOBAKL_INITIALIZER(ident) \
    tb::serializer::internal::RegisterCtor(t_name, Constructor_##to_t_ident);\
    tb::serializer::internal::RegisterDtor(t_name, Destructor_##to_t_ident);\
    tb::serializer::internal::RegisterJ2OFn(t_name, Json2Object_##to_t_ident);\
    tb::serializer::internal::RegisterO2JFn(t_name, Object2Json_##to_t_ident);\
CPPP2JSON_END_GLOBAKL_INITIALIZER(ident)

#define CPPP2JSON_DEFINE_VALUE_NORMALZIER(from, to) \
template <> \
struct VAlueNormalizer<from> {
    typedef from T;
    static to Normalize(T a) {
        return (to)a;
    }
};

CPPP2JSON_DEFINE_VALUE_NORMALZIER(unsigned long, int)

#define CPPP2JSON_BEGIN_GLOBAKL_INITIALIZER(ident) \
struct cpp2json_global_initializer_##ident {\
    cpp2json_global_initializer_##ident() {

#define CPPP2JSON_END_GLOBAKL_INITIALIZER(ident) \
    } \
} a_cpp2json_global_initializer_##ident;

#define CPP2JSON_DEFINE_SIMPLE_TYPE_GLOBAL_INITIALIZER(x, ident) \
CPPP2JSON_BEGIN_GLOBAKL_INITIALIZER(ident) \
    tb::serializer::internal::RegisterConstructor(#x, Constructor_##ident);\
    tb::serializer::internal::RegisterDestructor(#x, Destructor_##ident);\
    tb::serializer::internal::RegisterJ2OFn(#x, Json2Object_##ident);\
    tb::serializer::internal::RegisterO2JFn(#x, Object2Json_##ident);\
CPPP2JSON_END_GLOBAKL_INITIALIZER(ident)

CPP2JSON_DECLARE_SIMPLE_VALUE(bool, bool)
CPP2JSON_DECLARE_SIMPLE_VALUE(char, char)
CPP2JSON_DECLARE_SIMPLE_VALUE(unsigned char, unsigned_char)
CPP2JSON_DECLARE_SIMPLE_VALUE(short, short)
CPP2JSON_DECLARE_SIMPLE_VALUE(unsigned short, unsigned_short)
CPP2JSON_DECLARE_SIMPLE_VALUE(int, int)
CPP2JSON_DECLARE_SIMPLE_VALUE(unsigned int, unsigned_int)
CPP2JSON_DECLARE_SIMPLE_VALUE(long, long)
CPP2JSON_DECLARE_SIMPLE_VALUE(unsigned long, unsigned_long)
CPP2JSON_DECLARE_SIMPLE_VALUE(long long, long long)
CPP2JSON_DECLARE_SIMPLE_VALUE(unsigned long long, unsigned_long_long)
CPP2JSON_DECLARE_SIMPLE_VALUE(double, double)
CPP2JSON_DECLARE_SIMPLE_VALUE(float, float)

#pragma warning(pop)

} // end namsepsace internal

} // end namespace serializer

} // end namespace tb
